package icu.zhhll.lp.strategy;

import icu.zhhll.lp.LpExpression;
import icu.zhhll.lp.LpParam;
import icu.zhhll.lp.LpSolveException;
import icu.zhhll.lp.LpSolver;
import org.apache.commons.lang3.StringUtils;
import org.ojalgo.optimisation.ExpressionsBasedModel;
import org.ojalgo.optimisation.Optimisation;
import org.ojalgo.optimisation.Variable;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 使用 scip
 *
 * @author zh
 * @date 2022/12/16 16:58
 */
public class SCIPStrategy implements LPStrategy {

    // 生成lp文件路径
    private final String lpFilePath;

    public SCIPStrategy(String lpFilePath) {
        this.lpFilePath = lpFilePath;
    }

    @Override
    public BigDecimal[] solve(LpSolver lpSolver) {
        String lpFile = generateLpFile(lpSolver);
        String execResult = exec(lpFile);

        return analysisResult(execResult, lpSolver);
    }

    @Override
    public boolean preValidation(LpSolver lpSolver) {
        // 暂不支持 之后使用SCIP时在进行编写
        return true;
    }

    public BigDecimal[] analysisResult(String result, LpSolver lpSolver) {
        Map<String, BigDecimal> valueMap = new HashMap<>();
        String[] lines = result.split("\n");
        int resultLineStartNo = 0;
        int resultLineEndNo = 0;
        for (int lineNo = 0; lineNo < lines.length; lineNo++) {
            if (StringUtils.equals(lines[lineNo], "SCIP Status        : problem is solved [optimal solution found]")) {
                System.out.println("执行成功");
            }

            if (StringUtils.equals(lines[lineNo], "primal solution (original space):")) {
                System.out.println("执行结果开始");
                resultLineStartNo = lineNo + 3;
            }

            if (StringUtils.equals(lines[lineNo], "Statistics")) {
                System.out.println("执行结果结束");
                resultLineEndNo = lineNo - 2;
                break;
            }

        }

        for (int lineNo = resultLineStartNo; lineNo <= resultLineEndNo; lineNo++) {
            String line = lines[lineNo];
            if (lineNo == resultLineStartNo) { // 结果
                if (StringUtils.startsWith(line, "objective value:")) {
                    String replace = StringUtils.replace(line, "objective value:", "");
                    String trim = StringUtils.trim(replace);
                    double value = Double.parseDouble(trim);
                    System.out.println("结果为" + value);
                } else {
                    throw new LpSolveException("解析错误");
                }
            } else {
                int index = line.indexOf(" ");
                // 变量名
                String varName = line.substring(0, index);
                String[] split = line.substring(index).split("\t");
                String trim = split[0].trim();

                double val = Double.parseDouble(trim);
                valueMap.put(varName, BigDecimal.valueOf(val));
            }

        }

        int validCount = lpSolver.getValidCount();
        BigDecimal[] sols = new BigDecimal[validCount];
        Map<Integer, String> indexName = lpSolver.getIndexName();

        for (int i = 0; i < validCount; i++) {
            String varName = indexName.get(i);
            sols[i] = valueMap.getOrDefault(varName, BigDecimal.ZERO);
        }

        return sols;


    }

    public String exec(String lpFile) {
        Process proc;
        try {
            StringBuilder result = new StringBuilder();

            String cmd = "scip -f " + lpFile;

            proc = Runtime.getRuntime()
                    .exec(cmd);// 执行py文件
            //用输入输出流来截取结果
            BufferedReader in = new BufferedReader(new InputStreamReader(proc.getInputStream()));

            BufferedReader stdError = new BufferedReader(new
                    InputStreamReader(proc.getErrorStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line).append("\n");
//                System.out.println(line);
            }
            StringBuilder error = new StringBuilder();
            String s;
            while ((s = stdError.readLine()) != null) {
                error.append(s).append("\n");
//                System.out.println(s);
            }
            in.close();
            stdError.close();


            proc.waitFor();

            if (error.length() > 0) {
                throw new LpSolveException("执行失败,原因为" + error.toString());
            }

            return result.toString();
        } catch (IOException | InterruptedException e) {
            throw new LpSolveException("执行失败", e);
        }


    }

    /**
     * 生成lp问题文件
     *
     * @param lpSolver
     * @return
     */
    private String generateLpFile(LpSolver lpSolver) {
        ExpressionsBasedModel model = lpSolver.getModel();
        StringBuilder fileContent = new StringBuilder();
        if (model.getOptimisationSense() == Optimisation.Sense.MIN) {
            fileContent.append("minimize");
        } else {
            fileContent.append("maximize");
        }
        fileContent.append("\n");
        // 目标函数
        fileContent.append("cons: ").append(appendCons(model));
        // 约束条件
        fileContent.append("Subject To").append("\n");
        fileContent.append(appendExps(lpSolver));

        String genParams = appendGeneralVarNames(lpSolver);
        if (StringUtils.isNotBlank(genParams)) {
            fileContent.append("general").append("\n")
                    .append(genParams);
        }

        String binParams = appendBinaryVarNames(lpSolver);
        if (StringUtils.isNotBlank(genParams)) {
            fileContent.append("binary").append("\n")
                    .append(binParams);
        }
        fileContent.append("END");

        long curTime = System.currentTimeMillis();

        String fileName = lpSolver.getLpName() + "_" + curTime + ".lp";
        return writeTo(fileName, fileContent.toString());
    }

    private String writeTo(String fileName, String content) {
        try {
            Path file = Files.createFile(Paths.get(lpFilePath, fileName));
            Path path = Files.write(file, content.getBytes(StandardCharsets.UTF_8));
            return path.toFile().getPath();
        } catch (IOException e) {
            throw new LpSolveException("写入文件失败", e);
        }

    }


    // 目标函数
    private String appendCons(ExpressionsBasedModel model) {
        List<Variable> variables = model.getVariables();
        StringBuilder sb = new StringBuilder();
        boolean first = true;
        for (Variable variable : variables) {
            if (variable.getContributionWeight() == null) {
                continue;
            }
            double doubleValue = 0;
            try {
                doubleValue = variable.getContributionWeight().doubleValue();
            } catch (Exception e) {
                e.printStackTrace();
            }

            if (doubleValue == 0) {
                continue;
            }
            if (!first && doubleValue > 0) {
                sb.append(" + ");
            }
            first = false;
            sb.append(doubleValue).append(" ")
                    .append(variable.getName());
        }
        sb.append("\n");
        return sb.toString();
    }

    // 整型的变量
    private String appendGeneralVarNames(LpSolver lpSolver) {
        StringBuilder sb = new StringBuilder();

        int validCount = lpSolver.getValidCount();

        Map<Integer, String> indexName = lpSolver.getIndexName();

        for (int i = 0; i < validCount; i++) {
            sb.append(indexName.get(i)).append(" ");
        }
        if (sb.length() > 0) {
            sb.append("\n");
        }

        return sb.toString();
    }

    // 二进制变量
    private String appendBinaryVarNames(LpSolver lpSolver) {
        StringBuilder sb = new StringBuilder();

        int validCount = lpSolver.getValidCount();
        int inValidCount = lpSolver.getInValidCount();

        Map<Integer, String> indexName = lpSolver.getIndexName();

        for (int i = validCount; i < validCount + inValidCount; i++) {
            sb.append(indexName.get(i)).append(" ");
        }
        if (sb.length() > 0) {
            sb.append("\n");
        }

        return sb.toString();

    }


    private String appendExp(LpSolver lpSolver, String expName, LpExpression expression) {
        StringBuilder sb = new StringBuilder();
        sb.append(expName).append(": ");
        boolean first = true;
        for (LpParam lpParam : expression.getParams()) {
            // 索引
            int index;
            if (lpParam.getVarIndex() == null) {
                index = lpSolver.getNameIndex().get(lpParam.getVarName());
            } else {
                index = lpParam.getVarIndex();
            }
            String name = lpSolver.getIndexName().get(index);
            if (!first && lpParam.getWeight() > 0) { // 不是第一个值且权重为正
                sb.append(" + ");
            }
            first = false;
            sb.append(lpParam.getWeight()).append(" ").append(name);
        }
        if (expression.getSign() == LpExpression.Sign.GT) {
            sb.append(" >= ");
        } else if (expression.getSign() == LpExpression.Sign.LT) {
            sb.append(" <= ");
        } else {
            sb.append(" = ");
        }
        sb.append(expression.getVal()).append("\n");

        return sb.toString();
    }

    public String appendExps(LpSolver lpSolver) {
        Map<String, Map<String, LpExpression>> lpExpressionMap = lpSolver.getLpExpressionMap();

        StringBuilder sb = new StringBuilder();

        for (Map<String, LpExpression> values : lpExpressionMap.values()) {
            for (Map.Entry<String, LpExpression> entry : values.entrySet()) {
                LpExpression expression = entry.getValue();
                sb.append(appendExp(lpSolver, entry.getKey(), expression));
            }
        }


        return sb.toString();
    }
}
